package com.yingwei.mining.control;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import java.util.Iterator;

import org.hibernate.Session;

import com.yingwei.mining.model.businessobject.Configuration;
import com.yingwei.mining.model.businessobject.Device;
import com.yingwei.mining.model.businessobject.Edge;
import com.yingwei.mining.model.businessobject.Excavator;
import com.yingwei.mining.model.businessobject.Node;
import com.yingwei.mining.model.businessobject.Route;
import com.yingwei.mining.model.businessobject.UnloadArea;
import com.yingwei.mining.util.HibernateUtil;

public class DataProcess
{
    public static void main(String[] arg)
    {
	Data.getData().init();
	new DataProcess().postInitProcess();
	Data.getData().flush();
	Data.getData().close();
    }

    public void postInitProcess()
    {
	doCalculatedScreenWidthHeight();
	doUpdateNodeDevice();
	doCalculateBestRoutes();
	Data.getData().flush();
    }

    private void doCalculatedScreenWidthHeight()
    {
	/**
	 * calculate calculalatedScreenWidth/Height in configuration
	 */
	Configuration configuration = Data.getData().getConfiguration();
	int x = configuration.getNorthWestLongitude();
	int y = configuration.getNorthWestLatitude();
	int x1 = configuration.getSouthEastLongitude();
	int y1 = configuration.getSouthEastLatitude();
	double screenWidth = ((x1 - x) / 100.0) / configuration.getMmPerPixel()
		* 6 * configuration.getMeterPerNauticalMile();
	double screenHeight = ((y - y1) / 100.0)
		/ configuration.getMmPerPixel() * 6
		* configuration.getMeterPerNauticalMile();
	// update calculated area
	configuration.setCalculatedScreenWidth((int) screenWidth);
	configuration.setCalculatedScreenHeight((int) screenHeight);
    }

    private void doUpdateNodeDevice()
    {
	/**
	 * post processing. device can't be instanced or narrowed to excavator,
	 * truck or unloadarea because hibernate used proxy objects. device
	 * field of node needs to be set up in the following code:
	 */
	List<Node> nodes = Data.getData().getNodes();
	List<Excavator> excavators = Data.getData().getExcavators();
	List<UnloadArea> unloadAreas = Data.getData().getUnloadAreas();
	for (int i = 0; i < nodes.size(); i++)
	{
	    Device d = nodes.get(i).getDevice();
	    if (d != null)
	    {
		Excavator ex = (Excavator) findObject(excavators, d.getId());
		if (ex != null)
		{
		    nodes.get(i).setDevice(ex);
		}
		else
		{
		    UnloadArea un = (UnloadArea) findObject(unloadAreas, d
			    .getId());
		    if (un != null)
		    {
			nodes.get(i).setDevice(un);
		    }
		    else
		    {
			System.out.println("something is wrong" + d.getId());
		    }
		}
	    }
	}
    }

    private void doCalculateBestRoutes()
    {
	/**
	 * calculate the routes from edges.
	 */
	// calculate the new routes
	List<Route> result = new ArrayList<Route>();
	List<Node> nodes = Data.getData().getNodes();
	Iterator<Node> itrEx = nodes.iterator();
	while (itrEx.hasNext())
	{
	    Node nodeEx = itrEx.next();
	    if (nodeEx.getDevice() != null
		    && nodeEx.getDevice() instanceof Excavator)
	    {
		Iterator<Node> itrUa = nodes.iterator();
		while (itrUa.hasNext())
		{
		    Node nodeUa = itrUa.next();
		    if (nodeUa.getDevice() != null
			    && nodeUa.getDevice() instanceof UnloadArea)
		    {
			NodeWrapper dest = findShortestRoute(nodeEx, nodeUa);
			if (dest != null)
			{
			    Route route = new Route(nodeEx, nodeUa, dest.value,
				    dest.routeEdge, new Date(), new Date());
			    result.add(route);
			}
		    }
		}
	    }
	}
	// check if update database table is necessary
	boolean dirty = false;
	if( result.size() != Data.getData().getRoutes().size() )
	{
	    dirty = true;
	}
	else
	{
	    for( Route rt:result )
	    {
		boolean matched = false;
		for( Route rtdb : Data.getData().getRoutes() )
		{
		    if( rt.getNodeFrom() == rtdb.getNodeFrom() && 
			rt.getNodeTo() == rtdb.getNodeTo() )
		    {
			if( rt.getValue() != rtdb.getValue())
			{
			    dirty = true;
			    break;
			}
			matched = true;
		    }
		}
		if( !matched )
		{
		    dirty = true;
		    break;
		}
	    }
	}
	if(dirty)
	{
	    // clear route and route_edge table.
	    for (Route rt : Data.getData().getRoutes())
	    {
		Data.getData().deleteRoute(rt);
	    }
	    // add newly calculated table
	    for (Route rt : result)
	    {
		Data.getData().addRoute(rt);
	    }
	    System.out.println("dirty");
	}
    }

    @SuppressWarnings("unchecked")
    private Device findObject(List arr, long id)
    {
	for (int i = 0; i < arr.size(); i++)
	{
	    Device d = (Device) arr.get(i);
	    if (d.getId() == id)
	    {
		return d;
	    }
	}
	return null;
    }

    class NodeWrapper
    {
	boolean resolved;
	int value;
	Node node;
	Set<Edge> routeEdge;

	NodeWrapper(boolean resolved, int value, Node node)
	{
	    this.resolved = resolved;
	    this.value = value;
	    this.node = node;
	    this.routeEdge = new HashSet<Edge>();
	}

	int calculateDistance(Node node)
	{
	    int dx = this.node.getX() - node.getDx();
	    int dy = this.node.getY() - node.getDy();
	    double distance = Math.sqrt(dx * dx + dy * dy);
	    return (int) distance;
	}
    }

    /**
     * 
     * @param from
     * @param to
     *            dijkstra algorithm
     * @return
     */
    public NodeWrapper findShortestRoute(Node nodeFrom, Node nodeTo)
    {
	List<NodeWrapper> resolved = new ArrayList<NodeWrapper>();
	Map<Long, NodeWrapper> all = new HashMap<Long, NodeWrapper>();
	Iterator<Node> itr = Data.getData().getNodes().iterator();
	while (itr.hasNext())
	{
	    Node node = itr.next();
	    if (node.getId() == nodeFrom.getId())
	    {

		resolved.add(new NodeWrapper(true, 0, node));
	    }
	    all.put(node.getId(), new NodeWrapper(false, Integer.MAX_VALUE,
		    node));
	}
	return findNextShortestNeighbor(all, resolved, nodeTo);
    }

    private NodeWrapper findNextShortestNeighbor(Map<Long, NodeWrapper> all,
	    List<NodeWrapper> resolved, Node target)
    {
	int smallestValue = Integer.MAX_VALUE;
	NodeWrapper smallestNodeWrapper = null;
	Iterator<NodeWrapper> resolvedItr = resolved.iterator();
	while (resolvedItr.hasNext())
	{
	    NodeWrapper resolvedNode = resolvedItr.next();
	    Iterator<Edge> itrFrom = resolvedNode.node.getFromEdges()
		    .iterator();
	    Iterator<Edge> itrTo = resolvedNode.node.getToEdges().iterator();
	    while (itrFrom.hasNext() || itrTo.hasNext())
	    {
		Edge edge;
		Node nextNode;
		if (itrFrom.hasNext())
		{
		    edge = itrFrom.next();
		    nextNode = edge.getToNode();
		}
		else
		{
		    edge = itrTo.next();
		    nextNode = edge.getFromNode();
		}
		NodeWrapper nextNodeWrapper = all.get(nextNode.getId());
		if (!nextNodeWrapper.resolved)
		{
		    int dist = resolvedNode.calculateDistance(nextNode);
		    if (resolvedNode.value + dist <= nextNodeWrapper.value)
		    {
			nextNodeWrapper.value = resolvedNode.value + dist;
			nextNodeWrapper.routeEdge.clear();
			Iterator<Edge> itrRouteEdge = resolvedNode.routeEdge
				.iterator();
			while (itrRouteEdge.hasNext())
			{
			    nextNodeWrapper.routeEdge.add(itrRouteEdge.next());
			}
			nextNodeWrapper.routeEdge.add(edge);
		    }
		    if (nextNodeWrapper.value <= smallestValue)
		    {
			smallestNodeWrapper = nextNodeWrapper;
		    }
		}
	    }
	}
	if (smallestNodeWrapper == null)
	{
	    return null;
	}
	smallestNodeWrapper.resolved = true;
	resolved.add(smallestNodeWrapper);
	if (smallestNodeWrapper.node.getId() == target.getId())
	{
	    return smallestNodeWrapper;
	}
	else
	{
	    return findNextShortestNeighbor(all, resolved, target);
	}
    }
}
